﻿Namespace Global.Runa.UI.Threading

    Public Class DispatcherTimer

        Public Event Tick As EventHandler

        Private _dispatcher As Dispatcher
        Friend _dueTimeInTicks As Integer
        Private _instanceLock As Object
        Private _interval As TimeSpan
        Private _isEnabled As Boolean
        Private _operation As DispatcherOperation
        Private _priority As DispatcherPriority
        Private _tag As Object

        Public Sub New()
            Me.New(DispatcherPriority.Background)
        End Sub

        Public Sub New(ByVal priority As DispatcherPriority)
            Me._instanceLock = New Object
            Me.Initialize(Dispatcher.CurrentDispatcher, priority, TimeSpan.FromMilliseconds(0))
        End Sub

        Public Sub New(ByVal priority As DispatcherPriority, ByVal dispatcher As Dispatcher)
            Me._instanceLock = New Object
            If (dispatcher Is Nothing) Then
                Throw New ArgumentNullException("dispatcher")
            End If
            Me.Initialize(dispatcher, priority, TimeSpan.FromMilliseconds(0))
        End Sub

        Public Sub New(ByVal interval As TimeSpan, ByVal priority As DispatcherPriority, ByVal callback As EventHandler, ByVal dispatcher As Dispatcher)
            Me._instanceLock = New Object
            If (callback Is Nothing) Then
                Throw New ArgumentNullException("callback")
            End If
            If (dispatcher Is Nothing) Then
                Throw New ArgumentNullException("dispatcher")
            End If
            If (interval.TotalMilliseconds < 0) Then
                Throw New ArgumentOutOfRangeException("interval", "TimeSpanPeriodOutOfRange_TooSmall")
            End If
            If (interval.TotalMilliseconds > 2147483647) Then
                Throw New ArgumentOutOfRangeException("interval", "TimeSpanPeriodOutOfRange_TooLarge")
            End If
            Me.Initialize(dispatcher, priority, interval)
            AddHandler Tick, callback
            Me.Start()
        End Sub

        Private Function FireTick(ByVal unused As Object) As Object
            Me._operation = Nothing
            RaiseEvent Tick(Me, EventArgs.Empty)
            If Me._isEnabled Then
                Me.Restart()
            End If
            Return Nothing
        End Function

        Private Sub Initialize(ByVal dispatcher As Dispatcher, ByVal priority As DispatcherPriority, ByVal interval As TimeSpan)
            Dispatcher.ValidatePriority(priority, "priority")
            If (priority = DispatcherPriority.Inactive) Then
                Throw New ArgumentException("InvalidPriority", "priority")
            End If
            Me._dispatcher = dispatcher
            Me._priority = priority
            Me._interval = interval
        End Sub

        Friend Sub Promote()
            SyncLock Me._instanceLock
                If (Not Me._operation Is Nothing) Then
                    Me._operation.Priority = Me._priority
                End If
            End SyncLock
        End Sub

        Private Sub Restart()
            SyncLock Me._instanceLock
                If (Me._operation Is Nothing) Then
                    Me._operation = Me._dispatcher.BeginInvoke(DispatcherPriority.Inactive, New DispatcherOperationCallback(AddressOf Me.FireTick), Nothing)
                    Me._dueTimeInTicks = (Environment.TickCount + CInt(Me._interval.TotalMilliseconds))
                    If ((Me._interval.TotalMilliseconds = 0) AndAlso Me._dispatcher.CheckAccess) Then
                        Me.Promote()
                    Else
                        Me._dispatcher.AddTimer(Me)
                    End If
                End If
            End SyncLock
        End Sub

        Public Sub Start()
            SyncLock Me._instanceLock
                If Not Me._isEnabled Then
                    Me._isEnabled = True
                    Me.Restart()
                End If
            End SyncLock
        End Sub

        Public Sub [Stop]()
            Dim flag As Boolean = False
            SyncLock Me._instanceLock
                If Me._isEnabled Then
                    Me._isEnabled = False
                    flag = True
                    If (Not Me._operation Is Nothing) Then
                        Me._operation.Abort()
                        Me._operation = Nothing
                    End If
                End If
            End SyncLock
            If flag Then
                Me._dispatcher.RemoveTimer(Me)
            End If
        End Sub

        Public ReadOnly Property Dispatcher As Dispatcher
            Get
                Return Me._dispatcher
            End Get
        End Property

        Public Property Interval As TimeSpan
            Get
                Return Me._interval
            End Get
            Set(ByVal value As TimeSpan)
                Dim flag As Boolean = False
                If (value.TotalMilliseconds < 0) Then
                    Throw New ArgumentOutOfRangeException("value", "TimeSpanPeriodOutOfRange_TooSmall")
                End If
                If (value.TotalMilliseconds > 2147483647) Then
                    Throw New ArgumentOutOfRangeException("value", "TimeSpanPeriodOutOfRange_TooLarge")
                End If
                SyncLock Me._instanceLock
                    Me._interval = value
                    If Me._isEnabled Then
                        Me._dueTimeInTicks = (Environment.TickCount + CInt(Me._interval.TotalMilliseconds))
                        flag = True
                    End If
                End SyncLock
                If flag Then
                    Me._dispatcher.UpdateWin32Timer()
                End If
            End Set
        End Property

        Public Property IsEnabled As Boolean
            Get
                Return Me._isEnabled
            End Get
            Set(ByVal value As Boolean)
                SyncLock Me._instanceLock
                    If (Not value AndAlso Me._isEnabled) Then
                        Me.Stop()
                    ElseIf (value AndAlso Not Me._isEnabled) Then
                        Me.Start()
                    End If
                End SyncLock
            End Set
        End Property

        Public Property Tag As Object
            Get
                Return Me._tag
            End Get
            Set(ByVal value As Object)
                Me._tag = value
            End Set
        End Property

    End Class

End Namespace